﻿using AOT;
using Unity.Burst;
using UnityEngine.Animations;
using UnityEngine.Playables;
using AnimCollections;

namespace DA.AnimGraph {
    [BurstCompile]
    public unsafe partial struct BlendSpace1DNode : INode {

        public struct SetupData : INodeSetupData {
            public FloatLinkDesc inputDesc;
            public ClipLinkDesc[] clipsDesc;
            public int[] clipIds; // 长度和clipsPos一样，内容是clipsDesc的序号
            public float[] clipsPos; // 要求已经从小到大排好
        }

        //
        private FloatLink linkInput;
        private NativeSimpleArray<float> weights;
        private NativeSimpleArray<int> clipIds;
        private NativeSimpleArray<float> clipsPos;
        private NativeSimpleArray<AnimationClipPlayable> childPlayables;
        private NativeSimpleArray<ClipLink> linkChildClips;

        //
        private AnimationMixerPlayable playable;
        private TimeState timeState;
        private int lastIndex; // 记录上次的结果在 [lastIndex, lastIndex+1) 范围内

        [BurstCompile]
        [MonoPInvokeCallback(typeof(NodeLink.DisposeDelegate))]
        private static bool Dispose(in void* node) {
            var self = (BlendSpace1DNode*)node;

            for (int i = 0; i < self->childPlayables.Length; i++) {
                self->childPlayables[i].Destroy();
            }
            self->childPlayables.Dispose();

            self->weights.Dispose();
            self->linkChildClips.Dispose();

            return false;
        }

        private void Setup(in SetupData data, ref GraphBuffer buffer, in NodeLink* link) {
            linkInput = new FloatLink(data.inputDesc);

            int childrenCount = data.clipsDesc.Length;
            buffer.MallocNativeSimpleArray(out linkChildClips, childrenCount);
            for (int i = 0; i < childrenCount; i++) {
                linkChildClips[i] = new(data.clipsDesc[i]);
            }

            buffer.MallocNativeSimpleArray(out weights, childrenCount);
            buffer.MallocNativeSimpleArray(out clipIds, data.clipIds);
            buffer.MallocNativeSimpleArray(out clipsPos, data.clipsPos);
            buffer.MallocNativeSimpleArray(out childPlayables, childrenCount);
        }

        private static Playable BuildPlayableGraph(in void* node, in NodeLink* link, in PlayableGraph graph, in BuildContext buildContext) {
            var self = (BlendSpace1DNode*)node;

            self->timeState = new(TimeState.AccumulateMode.Normalize, true);
            link->SetTimeState(self->timeState);
            self->lastIndex = -1;

            int childrenCount = self->linkChildClips.Length;
            self->playable = AnimationMixerPlayable.Create(graph, childrenCount);
            for (int i = 0; i < childrenCount; i++) {
                ref var clipLink = ref self->linkChildClips.GetRefAt(i);
                var animClip = clipLink.Get(buildContext);
                self->childPlayables[i] = AnimationClipPlayable.Create(graph, animClip);
                self->childPlayables[i].SetApplyFootIK(false);
                self->playable.ConnectInput(i, self->childPlayables[i], 0);
            }

            return self->playable;
        }


        [BurstCompile]
        [MonoPInvokeCallback(typeof(NodeLink.EvaluateDelegate))]
        private static void Evaluate(in void* node, bool onEnter, in EvaluateContext evaluateContext, in GraphContext graphContext, ref UpstreamData upstream, ref ScopeContext scope) {
            var self = (BlendSpace1DNode*)node;

            int childrenCount = self->linkChildClips.Length;
            if (childrenCount == 0) return;
            if (childrenCount == 1) {
                self->timeState.Accumulate(evaluateContext.deltaTime);
                return;
            }

            NodeUtility.ClearWeights(ref self->weights, childrenCount);

            float inputPos = self->linkInput.Evaluate(onEnter, evaluateContext, graphContext, scope);
            self->EvaluateWeight(inputPos);
            self->NormalizeWeights();
            self->EvaluateTime(evaluateContext);
        }
        private void EvaluateWeight(float inputPos) {
            int posCount = clipsPos.Length;
            if (lastIndex < 0 || lastIndex > posCount) {
                lastIndex = posCount / 2;
            }

            for (int i = 0; i < posCount; i++) {
                float curPos = clipsPos[lastIndex];

                if (curPos.IsNear(inputPos)) {
                    weights[clipIds[lastIndex]] = 1;
                    return;
                }

                if (inputPos > curPos) {
                    if (lastIndex == posCount - 1) {
                        // 输入比最大的还大
                        weights[clipIds[lastIndex]] = 1;
                        return;
                    } else if (inputPos < clipsPos[lastIndex + 1]) {
                        // 在两者之间
                        float min = clipsPos[lastIndex];
                        float max = clipsPos[lastIndex + 1];
                        float w = (inputPos - min) / (max - min);
                        weights[clipIds[lastIndex]] += 1 - w;
                        weights[clipIds[lastIndex + 1]] += w;
                        return;
                    } else {
                        lastIndex += 1;
                    }
                } else {
                    if (lastIndex == 0) {
                        // 输入比最小的还小
                        weights[clipIds[0]] = 1;
                        return;
                    } else if (inputPos > clipsPos[lastIndex - 1]) {
                        // 在两者之间
                        lastIndex -= 1; // 让lastIndex成为较小的那个

                        float min = clipsPos[lastIndex];
                        float max = clipsPos[lastIndex + 1];
                        float w = (inputPos - min) / (max - min);
                        weights[clipIds[lastIndex]] += 1 - w;
                        weights[clipIds[lastIndex + 1]] += w;
                        return;
                    } else {
                        lastIndex -= 1;
                    }
                }
            }

        }
        private void NormalizeWeights() {
            float w = 0;
            for (int i = 0; i < weights.Length; i++) {
                w += weights[i];
            }
            if (w == 0) return;
            if (w.IsNear(1)) return;
            w = 1 / w;
            for (int i = 0; i < weights.Length; i++) {
                weights[i] *= w;
            }
        }
        private void EvaluateTime(in EvaluateContext evaluateContext) {
            float duration = 0;
            for (int i = 0; i < linkChildClips.Length; i++) {
                duration += weights[i] * linkChildClips[i].duration;
            }
            timeState.SetDuration(duration);
            timeState.Accumulate(evaluateContext.deltaTime);
        }
        [BurstCompile]
        [MonoPInvokeCallback(typeof(NodeLink.ApplyDelegate))]
        private static void Apply(in void* node) {
            var self = (BlendSpace1DNode*)node;

            int childCount = self->linkChildClips.Length;
            if (childCount == 0) return;
            if (childCount == 1) {
                self->playable.SetInputWeight(0, 1);
                self->childPlayables[0].SetTime(self->timeState.time);
                return;
            }

            float normalizedTime = self->timeState.time;
            for (int i = 0; i < self->childPlayables.Length; i++) {
                float w = self->weights[i];
                self->playable.SetInputWeight(i, w);

                self->childPlayables[i].SetTime(self->linkChildClips[i].duration * normalizedTime);
            }
        }

    }
}